#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QPainter>
#include <QLineEdit>
#include <QMessageBox>

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    connect(ui->humanInputEdit, SIGNAL(returnPressed()), this, SLOT(receiveHumanInput()));
    connect(ui->actionstart_with_AI_first, SIGNAL(triggered(bool)), this, SLOT(newGameWithAIFirst()));
    connect(ui->actionstart_with_human_first, SIGNAL(triggered(bool)), this, SLOT(newGameWithHumanFirst()));
    connect(ui->actionclear_judge_output, SIGNAL(triggered(bool)), this, SLOT(clearOutput()));

    vmap[1][2] = 1;
    vmap[1][3] = 2;
    vmap[2][3] = 3;
    vmap[2][4] = 4;
    vmap[2][5] = 5;
    vmap[3][5] = 6;
    vmap[3][6] = 7;
    vmap[4][5] = 8;
    vmap[5][6] = 9;
    vmap[4][7] = 10;
    vmap[4][8] = 11;
    vmap[5][8] = 12;
    vmap[5][9] = 13;
    vmap[6][9] = 14;
    vmap[6][10] = 15;
    vmap[7][8] = 16;
    vmap[8][9] = 17;
    vmap[9][10] = 18;
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::paintEvent(QPaintEvent * event)
{
    static const QColor dotColor{46, 47, 48, 128};
    static const QColor edgeColor0{0, 176, 255, 160};
    static const QColor edgeColor1{244, 67, 54, 160};
    static const int mapOriginX = 265;
    static const int mapOriginY = 100;
    static const int edgeLength = 120;
    static const int dotRadius = 20;
    static const int edgeWidth = 10;

    static const int edge2p[19][2] = {
        0,0,
        1,2,
        1,3,
        2,3,
        2,4,
        2,5,
        3,5,
        3,6,
        4,5,
        5,6,
        4,7,
        4,8,
        5,8,
        5,9,
        6,9,
        6,10,
        7,8,
        8,9,
        9,10
    };
    static const double pointpos[11][2] = {
        0,0,
        1.5,0,
        1,0.866,
        2,0.866,
        0.5,1.732,
        1.5,1.732,
        2.5,1.732,
        0,2.598,
        1,2.598,
        2,2.598,
        3,2.598
    };
    static const int tri2p[10][3] = {
        0,0,0,
        1,2,3,
        2,4,5,
        2,3,5,
        3,5,6,
        4,7,8,
        4,5,8,
        5,8,9,
        5,6,9,
        6,9,10
    };

    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing);


    painter.setBrush(dotColor);
    painter.setPen(dotColor);

    QFont font;
    font.setPointSize(13);
    font.setFamily("Consolas");
    painter.setFont(font);

    QTextOption option(Qt::AlignCenter);

    painter.setBrush(QColor(100, 100, 100));
    painter.drawRect(0, 0, 190, 1000);
    painter.setBrush(QColor(64, 66, 68));
    painter.drawRect(190, 0, 1000, 1000);

    painter.translate(mapOriginX, mapOriginY);

    //draw regions
    for(int i = 0; i < 9; i++)
    {
        if(bt & (1 << i))
        {
            QPoint arr[3];
            for(int j = 0; j < 3; j++)
            {
                arr[j].rx() = pointpos[tri2p[i + 1][j]][0] * edgeLength;
                arr[j].ry() = pointpos[tri2p[i + 1][j]][1] * edgeLength;
            }
            painter.setBrush(edgeColor0);
            painter.drawConvexPolygon(arr, 3);
        }
        if(at & (1 << i))
        {
            QPoint arr[3];
            for(int j = 0; j < 3; j++)
            {
                arr[j].rx() = pointpos[tri2p[i + 1][j]][0] * edgeLength;
                arr[j].ry() = pointpos[tri2p[i + 1][j]][1] * edgeLength;
            }
            painter.setBrush(edgeColor1);
            painter.drawConvexPolygon(arr, 3);
        }
    }

    //draw edges
    for(int i = 0; i < 18; i++)
    {
        if(chess1 & (1 << i))
        {
            int x1 = pointpos[edge2p[i + 1][0]][0] * edgeLength;
            int y1 = pointpos[edge2p[i + 1][0]][1] * edgeLength;
            int x2 = pointpos[edge2p[i + 1][1]][0] * edgeLength;
            int y2 = pointpos[edge2p[i + 1][1]][1] * edgeLength;

            QPen pen(edgeColor1, edgeWidth, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
            painter.setPen(pen);
            painter.drawLine(x1, y1, x2, y2);
        }
        if(chess0 & (1 << i))
        {
            int x1 = pointpos[edge2p[i + 1][0]][0] * edgeLength;
            int y1 = pointpos[edge2p[i + 1][0]][1] * edgeLength;
            int x2 = pointpos[edge2p[i + 1][1]][0] * edgeLength;
            int y2 = pointpos[edge2p[i + 1][1]][1] * edgeLength;

            QPen pen(edgeColor0, edgeWidth, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
            painter.setPen(pen);
            painter.drawLine(x1, y1, x2, y2);
        }
    }

    // draw dots and texts
    int t = 1;
    for(int i = 0; i < 4; i++)
    {
        int offsetX = (3 - i) * edgeLength / 2;
        int offsetY = i * edgeLength * 0.866;
        for(int j = 0; j < i + 1; j++)
        {
            painter.setBrush(dotColor);
            painter.setPen(Qt::NoPen);
            painter.drawEllipse(QPoint(offsetX + j * edgeLength, offsetY), dotRadius, dotRadius);
            painter.setPen(Qt::white);
            painter.drawText(QRectF(offsetX + j * edgeLength - dotRadius, offsetY - dotRadius, 2 * dotRadius, 2 * dotRadius),
                             QString("%1").arg(t), option);
            t++;
        }
    }
}

void MainWindow::newGame()
{
    computerOutput("new game\ninput 0 for human first\ninput 1 for AI first\n");
    chess0 = 0;
    chess1 = 0;
    status = 0;
    a = 0;
    b = 0;
    at = 0;
    bt = 0;
    ui->beacon->setStyleSheet("background-color: red");
}

void MainWindow::newGameWithAIFirst()
{
    whofirst = 1;
    computerOutput("new game\nAI first\n");
    chess0 = 0;
    chess1 = 0;
    status = 1;
    a = 0;
    b = 0;
    at = 0;
    bt = 0;
    turn = 1;
    ui->beacon->setStyleSheet("background-color: red");
    AIInput();
}

void MainWindow::newGameWithHumanFirst()
{
    whofirst = 0;
    computerOutput("new game\nhuman first\n");
    chess0 = 0;
    chess1 = 0;
    status = 1;
    a = 0;
    b = 0;
    at = 0;
    bt = 0;
    turn = 0;
    ui->beacon->setStyleSheet("background-color: green");
}

void MainWindow::receiveHumanInput()
{
    int x, y;
    char buffer[50];

    QString str = ui->humanInputEdit->text();
    x = str.mid(0, 1).toInt();
    y = str.mid(1).toInt();

    if(status == 0)
    {
        if(x != 0 && x != 1)
        {
            computerOutput("invalid input, try again\n");
        }
        else
        {
            status = 1;
            turn = x;

            computerOutput("game started");
            if(turn)
            {
                computerOutput("computer first\n");
                AIInput();
            }
            else
            {
                computerOutput("human first\n");
            }
        }
    }
    else if(status == 1)
    {
        if(turn == 0)
        {
            if(x > y)
            {
                int t = x;
                x = y;
                y = t;
            }
            if(vmap[x][y] == 0)
            {
                computerOutput("invalid input, try again\n");
                return;
            }
            chess0 |= (1 << (vmap[x][y] - 1));
            sprintf(buffer, "human at edge%d", vmap[x][y]);
            computerOutput(buffer);
            updateTri(chess0 | chess1, vmap[x][y], bt);
            update();

            int s = score(vmap[x][y], chess0 | chess1);
            if(s != 0)
            {
                b += s;
                sprintf(buffer, "human gain %d", s);
                computerOutput(buffer);
            }
            else
            {
                turn = 1;
                ui->beacon->setStyleSheet("background-color: red");
            }

            if((chess0 | chess1) == 262143)
            {
                status = 2;
                if(a > b)
                    computerOutput("game over, computer wins\n");
                else
                    computerOutput("game over, human wins\n");
                ui->beacon->setStyleSheet("background-color: red");
            }
            else
            {
                if(turn) AIInput();
            }
        }
    }

    updateScoreDisplay();
    ui->humanInputEdit->clear();
}

void MainWindow::computerOutput(const char * str)
{
    ui->computerOut->append(QString(str));
    //QMessageBox::information(NULL, "aaa", "bbb");
}

void MainWindow::AIInput()
{
    int s = 1;
    char buffer[50];

    while(s && (chess0 | chess1) != 262143)
    {
        int bestMove = -1;
        if(whofirst == 0)
            alphabeta(chess0 | chess1, 10, -INF, INF, 1, a, b, bestMove);
        else
            alphabetaII(chess0 | chess1, 16, -INF, INF, 1, a, b, bestMove);

        sprintf(buffer, "AI at edge%d", bestMove);
        computerOutput(buffer);

        chess1 |= (1 << (bestMove - 1));
        updateTri(chess0 | chess1, bestMove, at);
        update();

        s = score(bestMove, chess0 | chess1);
        if(s > 0)
        {
            a += s;

            sprintf(buffer, "AI gain %d", s);
            computerOutput(buffer);
        }
    }

    if((chess1 | chess0) == FULL)
    {
        status = 2;
        if(a > b)
            computerOutput("game over, computer wins\n");
        else
            computerOutput("game over, human wins\n");
        ui->beacon->setStyleSheet("background-color: red");
    }
    else
    {
        turn = 0;
        ui->beacon->setStyleSheet("background-color: green");
    }

    updateScoreDisplay();
}

int MainWindow::score(int edge, int chess)
{
    static const int edge_tri[19][3] = {
        0,0,0,
        1,7,0,		//1
        1,7,0,
        2,7,52,
        1,152,0,
        2,52,152,	//5
        2,52,352,
        1,352,0,
        2,152,3200,
        2,352,12544,
        1,34304,0,	//10
        2,3200,34304,
        2,3200,71680,
        2,71680,12544,
        2,12544,155648,
        1,155648,0,	//15
        1,34304,0,
        1,71680,0,
        1,155648,0
    };

    int ans = 0;
    for (int i = 1; i <= edge_tri[edge][0]; i++)
    {
        if ((chess & edge_tri[edge][i]) == edge_tri[edge][i])
        {
            ans++;
        }
    }
    return ans;
}

void MainWindow::updateTri(int chess, int edge, int & tri)
{
    static const int edge_tri[19][3] = {
        0,0,0,
        1,7,0,		//1
        1,7,0,
        2,7,52,
        1,152,0,
        2,52,152,	//5
        2,52,352,
        1,352,0,
        2,152,3200,
        2,352,12544,
        1,34304,0,	//10
        2,3200,34304,
        2,3200,71680,
        2,71680,12544,
        2,12544,155648,
        1,155648,0,	//15
        1,34304,0,
        1,71680,0,
        1,155648,0
    };
    static const int ad_tri[19][3] = {
        0,0,0,
        1,1,0,      //1
        1,1,0,      //2
        2,1,3,      //3
        1,2,0,      //4
        2,3,2,      //5
        2,3,4,
        1,4,0,
        2,2,6,
        2,4,8,
        1,5,0,      //10
        2,6,5,
        2,6,7,
        2,7,8,
        2,8,9,
        1,9,0,      //15
        1,5,0,
        1,7,0,
        1,9,0
    };

    for (int i = 1; i <= edge_tri[edge][0]; i++)
    {
        if ((chess & edge_tri[edge][i]) == edge_tri[edge][i])
        {
            //for(int j = 1; j <= ad_tri[edge][0]; j++)
            //{
                tri |= (1 << (ad_tri[edge][i] - 1));
            //}
        }
    }
}

void MainWindow::updateScoreDisplay()
{
    char buffer[50];

    sprintf(buffer, "human score: %d", b);
    ui->scoreLbl0->setText(buffer);

    sprintf(buffer, "AI score: %d", a);
    ui->scoreLbl1->setText(buffer);
}

void MainWindow::clearOutput()
{
    ui->computerOut->clear();
}

int MainWindow::alphabeta(int chess, int depth, int alpha, int beta, int move, int a, int b, int & bestMove)
{
    if(a >= 5)
    {
        if(depth == 10)
            for(int i = 0; i < 18; i++)
                if((chess & (1 << i)) == 0)
                    bestMove = i + 1;
        return move ? INF : -INF;
    }
    if(b >= 5)
    {
        if(depth == 10)
            for(int i = 0; i < 18; i++)
                if((chess & (1 << i)) == 0)
                    bestMove = i + 1;
        return move ? -INF : INF;
    }


    int val = 0;
    bool flag = false;
    for(int i = 0; i < 18; i++)
    {
        if((chess & (1 << i)) == 0)
        {
            if(flag == false) bestMove = i + 1, flag = true;

            chess |= (1 << i);
            int s = score(i + 1, chess);
            if(s == 0)
            {
                int ph;
                val = -alphabeta(chess, depth - 1, -beta, -alpha, 1 - move, a, b, ph);
            }
            else
            {
                int ph;
                move ? a += s : b += s;
                val = alphabeta(chess, depth - 1, alpha, beta, move, a, b, ph);
                move ? a -= s : b -= s;
            }
            chess -= (1 << i);

            if(val >= beta)
            {
                bestMove = i + 1;
                return beta;
            }
            if(val > alpha)
            {
                bestMove = i + 1;
                alpha = val;
            }
        }
    }

    return alpha;
}

int MainWindow::alphabetaII(int chess, int depth, int alpha, int beta, int move, int a, int b, int & bestMove)
{
    if(chess == 0)
    {
        bestMove = 5;
        return -INF;
    }
    if(depth == 0 || chess == FULL)
    {
        return evaluate(chess, move, a, b);
    }

    int val = 0;
    int flag = false;
    for(int i = 0; i < 18; i++)
    {
        if((chess & (1 << i)) == 0)
        {
            if(flag == false) flag = true, bestMove = i + 1;

            chess |= (1 << i);
            int s = score(i + 1, chess);
            if(s == 0)
            {
                int ph;
                val = -alphabeta(chess, depth - 1, -beta, -alpha, 1 - move, a, b, ph);
            }
            else
            {
                int ph;
                move ? a += s : b += s;
                val = alphabeta(chess, depth - 1, alpha, beta, move, a, b, ph);
                move ? a -= s : b -= s;
            }
            chess -= (1 << i);

            if(val >= beta)
            {
                bestMove = i + 1;
                return beta;
            }
            if(val > alpha)
            {
                bestMove = i + 1;
                alpha = val;
            }
        }
    }

    return alpha;
}

int MainWindow::evaluate(int chess, int move, int a, int b)
{
    //if(chess == FULL)
        return move ? a - b : b - a;
}
